/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.gidoo.xmldb;

import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

/**
 *
 * @author thomas
 */
public class Parser
{

  public static final int ELEMENT_TYPE = 0;
  public static final int ATTRIBUTE_TYPE = 1;
  public static final int CDATA_TYPE = 2;
  private static Logger logger = Logger.getLogger(Parser.class.getName());

  private long eventCountForFlush = 50000;

  private Connection con;
  private XMLStreamReader parser;
  private long docID;
  private PreparedStatement psInsertNameSpace;
  private PreparedStatement psInsertElement;
  private PreparedStatement psInsertContents;
  private PreparedStatement psInsertAttributes;
  private PreparedStatement psInsertCharacters;
  private PreparedStatement psInsertChildren;
  private PreparedStatement psMaxContentID;
  private LinkedList<Long> parentStack;
  private HashMap<Long, Integer> childCount;
  private long eventCounter;
  private long flushCounter;
  private long maxContentID;

  public Parser(Connection con, XMLStreamReader parser, long docID)
  {
    this.con = con;
    this.parser = parser;
    this.docID = docID;

    this.maxContentID = 0;

    this.eventCounter = 0;
    this.flushCounter = 0;

    try
    {
      logger.fine("preparing statements before parsing");

      psInsertNameSpace = con.prepareStatement(
        "insert into uri_for_ns(docid, uri, prefix) values(" + docID + ",?,?)");

      psInsertContents = con.prepareStatement(
        "insert into contents(docid, id) values(" + docID + ",?)",
        PreparedStatement.RETURN_GENERATED_KEYS);

      psInsertElement = con.prepareStatement(
        "insert into elements(id, name, ns) values(?,?,?)");

      psInsertAttributes = con.prepareStatement(
        "insert into attributes(elementid, name, ns, value) values(?,?,?,?)",
        PreparedStatement.RETURN_GENERATED_KEYS);

      psInsertCharacters = con.prepareStatement(
        "insert into characters(id, value) values(?,?)");

      psInsertChildren = con.prepareStatement(
        "insert into children(idparent, idchild, orderval) values(?,?,?)");

      psMaxContentID = con.prepareStatement(
        "select max(id) from contents");

    }
    catch(SQLException ex)
    {
      Logger.getLogger(Parser.class.getName()).log(Level.SEVERE, null, ex);
    }

    parentStack = new LinkedList<Long>();
    childCount = new HashMap<Long, Integer>();

  }

  public void parse() throws XMLStreamException, SQLException
  {
    logger.fine("starting parsing");

    ResultSet rsMaxContentID = psMaxContentID.executeQuery();
    if(rsMaxContentID.next())
    {
      maxContentID = rsMaxContentID.getLong(1);
      maxContentID++;
      logger.fine("maxContentID=" + maxContentID);
    }
    else
    {
      logger.warning("could not gather maximum id from table content");
    }

    while(parser.hasNext())
    {
      eventCounter++;
      if(eventCounter % eventCountForFlush == 0)
      {
        logger.fine("parsing event nr. " + eventCounter + ", flush counter " + flushCounter);
      }

      switch(parser.getEventType())
      {
        case XMLStreamReader.START_ELEMENT:

          start_element();
          break;
        case XMLStreamReader.END_ELEMENT:
          parentStack.remove();
          break;
        case XMLStreamReader.CHARACTERS:
          character();
          break;
      }

      if(eventCounter % eventCountForFlush == 0)
      {
        flushBatchInserts();
      }

      parser.next();
    }

    flushBatchInserts();
    logger.fine("finished parsing event nr. " + eventCounter + ", flush counter " + flushCounter);

  }

  private void character() throws SQLException
  {

    long id = maxContentID++;

    psInsertContents.setLong(1, id);
    psInsertContents.addBatch();

    psInsertCharacters.setLong(1, id);
    String text = parser.getText();
    psInsertCharacters.setString(2, text);

    psInsertCharacters.addBatch();

    if(parentStack.size() > 0)
    {
      // add this as children
      insertAsChildren(id);
    }
  }

  private void start_element() throws SQLException
  {
    long id = maxContentID++;

    psInsertContents.setLong(1, id);
    psInsertContents.addBatch();

    psInsertElement.setLong(1, id);
    psInsertElement.setString(2, parser.getName().getLocalPart());
    psInsertElement.setString(3, parser.getName().getPrefix());

    psInsertElement.addBatch();

    // find attributes
    int attCount = parser.getAttributeCount();
    if(attCount > 0)
    {
      for(int i = 0; i < attCount; i++)
      {
        psInsertAttributes.setLong(1, id);
        psInsertAttributes.setString(2, parser.getAttributeName(i).getLocalPart());
        psInsertAttributes.setString(3, parser.getAttributeName(i).getPrefix());
        String val = parser.getAttributeValue(i);
        psInsertAttributes.setString(4, val);

        psInsertAttributes.addBatch();
      }
    }

    // check for namespaces
    if(parentStack.size() == 0)
    {
      // this is the root element
      int nsCount = parser.getNamespaceCount();
      for(int i = 0; i < nsCount; i++)
      {
        String prefix = parser.getNamespacePrefix(i);
        String uri = parser.getNamespaceURI(i);

        logger.fine("found namespace " + prefix + ":" + uri);

        psInsertNameSpace.setString(1, uri == null ? "" : uri);
        psInsertNameSpace.setString(2, prefix == null ? "" : prefix);

        psInsertNameSpace.addBatch();
      }
    }
    else
    {
      insertAsChildren(id);
    }
    parentStack.addFirst(id);
  }

  private void insertAsChildren(long elementID) throws SQLException
  {
    if(parentStack.size() == 0)
    {
      return;
    }

    long parentID = parentStack.getFirst();
    // insert children entry for this entry

    if(!childCount.containsKey(parentID))
    {
      childCount.put(parentID, 0);
    }
    int max = childCount.get(parentID);
    psInsertChildren.setLong(1, parentID);
    psInsertChildren.setLong(2, elementID);
    psInsertChildren.setInt(3, max + 1);

    psInsertChildren.addBatch();
    childCount.put(parentID, max + 1);

  }

  private void flushBatchInserts() throws SQLException
  {
    //logger.fine("starting to flush batched inserts");
    try
    {
      psInsertNameSpace.executeBatch();
      psInsertContents.executeBatch();
      psInsertCharacters.executeBatch();
      psInsertElement.executeBatch();
      psInsertAttributes.executeBatch();
      psInsertChildren.executeBatch();
    }
    catch(BatchUpdateException ex)
    {
      logger.log(Level.SEVERE, "batch update failed", ex);
      throw ex.getNextException();
    }
    flushCounter++;
    //logger.fine("finished to flush batched inserts");
  }

  /** Get the count of parser events when the import statements are flushed */
  public long getEventCountForFlush()
  {
    return eventCountForFlush;
  }

  /** Set the count of parser events when the import statements are flushed */
  public void setEventCountForFlush(long eventCountForFlush)
  {
    this.eventCountForFlush = eventCountForFlush;
  }



}